import { Warning, CodeBlockTransformer, Link } from '@brillout/docpress'

Some npm packages contain invalid JavaScript code.

See <Link href="#workaround" />.

> The reason why some npm packages contain invalid code is because the transpilation process of webpack-based frameworks, most notably Next.js, isn't strict and tolerates erroneous code: thus an npm package can work with Next.js while not work with Vite-based frameworks. You can learn more at <Link href="#why" />.
>
> The situation will improve and eventually be completely resolved as Vite-based frameworks gain popularity, which we can expect to happen quickly since all frameworks, except for Next.js, are using Vite nowadays.

## Workaround

You can workaround problematic npm packages by using on of the following:
 - `ssr.noExternal`
 - `vite-plugin-cjs-interop`
 - SSR opt-out

> The workarounds usually work. But if you struggle working around a broken npm package then feel free to <Link href="/faq#how-can-i-reach-out-for-help">reach out for help</Link>.


### `ssr.noExternal`

```ts
// vite.config.ts

import type { UserConfig } from 'vite'

export default {
  ssr: {
    // Add problematic npm package here:
    noExternal: ['some-npm-package']
  }
} satisfies UserConfig
```

See [Vite > `ssr.noExternal`](https://vitejs.dev/config/ssr-options.html#ssr-noexternal).

> You may need to add nested npm packages to `ssr.noExternal`, because CJS/ESM issues sometimes cascade down along the "`noExternal` boundary" as you add npm packages to `ssr.noExternal`.

> The section <Link href="#why" /> explains why `ssr.noExternal` is a workaround.

### `vite-plugin-cjs-interop`

```ts
// vite.config.ts

import type { UserConfig } from 'vite'
import { cjsInterop } from "vite-plugin-cjs-interop"

export default {
  plugins: [
    cjsInterop({
      // Add broken npm package here
      dependencies: [
        // Apply patch to root import:
        //   import someImport from 'some-package'
        "some-package",

        // Apply patch to all sub imports:
        //   import someImport from 'some-package/path'
        //   import someImport from 'some-package/sub/path'
        //   ...
        "some-package/**"
      ]
    })
  ]
} satisfies UserConfig
```

See [GitHub > `cyco130/vite-plugin-cjs-interop`](https://github.com/cyco130/vite-plugin-cjs-interop).

For the following errors, we recommend trying `vite-plugin-cjs-interop` before trying `ssr.noExternal`:

<Link href="#react-invalid-component">**React invalid component**</Link>

<CodeBlockTransformer lineBreak="white-space">
```
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: something-else.
```
</CodeBlockTransformer>

<Link href="#named-export-not-found">**Named export not found**</Link>

<CodeBlockTransformer lineBreak="white-space">
```
SyntaxError: Named export 'SomeImport' not found. The requested module 'some-library' is a CommonJS module, which may not support all module.exports as named exports.
```
</CodeBlockTransformer>

### SSR opt-out

As a last resort, if both `ssr.noExternal` and `vite-plugin-cjs-interop` doesn't work, you can opt out of SSR:
 - <Link href="/clientOnly" />
 - <Link href="/ssr" />

> Opting out of SSR almost always works, see explanation at <Link href="#why" />.


## Why

You may ask yourself how it's possible that an npm package can publish invalid JavaScript code that doesn't get fixed for months.

The main reason is that some frameworks such as Next.js transpile the server-side code of npm packages, whereas Vite transpiles only the client-side code of npm packages. When server-side code contains invalid JavaScript then Node.js crashes and throws one of <Link href="#common-errors">these errors</Link>, while transpilers are more tolerant and transform invalid JavaScript (that Node.js isn't able to execute) into valid JavaScript (that Node.js is able to execute).

By default, Vite doesn't transpile the server-side code of npm packages for a much faster DX, so that Node.js directly executes the server-side code without involving a slow transpilation process.

That's why <Link href="#workaround">adding an npm package to `ssr.noExternal`</Link> is usually a workaround when the npm package contains invalid JavaScript.
By adding an npm package to `ssr.noExternal`, you replicate the behavior of frameworks like Next.js.

> The issue is most widespread in the React ecosystem because of Next.js's prevalence, but this is going to less and less of an issue as Vite-based React frameworks gain popularity. All frameworks other than Next.js are now Vite-based (e.g. Remix), thus the situation will quickly improve.

## Common errors

Common invalid JavaScript code published by npm packages.

### Cannot use import statement outside a module

<CodeBlockTransformer lineBreak="white-space">
```
(node:30335) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
node_modules/some-library/dist/index.js:1
SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1033:15)
    at Module._compile (node:internal/modules/cjs/loader:1069:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Module._load (node:internal/modules/cjs/loader:827:12)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:170:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)

Node.js v18.0.0
```
</CodeBlockTransformer>

> Node.js's message `set "type": "module" in package.json or use the .mjs extension` is misleading because it means the library's `node_modules/some-library/package.json`, not your `package.json`. It isn't really actionable (unless you patch the library).

Recommended workaround:
 - <Link href="#ssr-noexternal" />

Alternatively, you can try to patch the broken npm package with:
 - [pnpm.packageExtensions](https://pnpm.io/package_json#pnpmpackageextensions)
 - [pnpm patch](https://pnpm.io/cli/patch)


### Named export not found

<CodeBlockTransformer lineBreak="white-space">
```
import { SomeImport } from "some-library";
         ^^^^^^^^^^
SyntaxError: Named export 'SomeImport' not found. The requested module 'some-library' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'some-library';
const { SomeImport } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)

Node.js v18.0.0
```
</CodeBlockTransformer>

For default exports, the workaround proposed by Node.js may not work and you may need to do this instead:

```js
import DefaultImport from "some-library";// [!code --]
import pkg from 'some-library';// [!code ++]
const DefaultImport = pkg.default ?? pkg;// [!code ++]
```

For a workaround that is global (and TypeScript friendly), see <Link href="#vite-plugin-cjs-interop" />.


### ERR_MODULE_NOT_FOUND

<CodeBlockTransformer lineBreak="white-space">
```
node:internal/process/esm_loader:91
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'node_modules/some-library/dist/some-file' imported from node_modules/some-library/dist/index.js
Did you mean to import some-file.js?
    at new NodeError (node:internal/errors:372:5)
    at finalizeResolution (node:internal/modules/esm/resolve:405:11)
    at moduleResolve (node:internal/modules/esm/resolve:966:10)
    at defaultResolve (node:internal/modules/esm/resolve:1176:11)
    at ESMLoader.resolve (node:internal/modules/esm/loader:605:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:318:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
    at link (node:internal/modules/esm/module_job:78:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.0.0
```
</CodeBlockTransformer>

> The error is usually thrown when the library's ESM code contains `import './some-file'`. (It should be `import './some-file.js'` instead, as imports in ESM code are required to include the file extension.)

Recommended workaround: <Link href="#ssr-noexternal" />.


### ERR_UNSUPPORTED_DIR_IMPORT

<CodeBlockTransformer lineBreak="white-space">
```
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import 'node_modules/some-library/dist/some-dir/' is not supported resolving ES modules imported from node_modules/some-library/dist/index.js
Did you mean to import ./some-dir/index.js?
    at finalizeResolution (node:internal/modules/esm/resolve:412:17)
```
</CodeBlockTransformer>

> ESM doesn't allow directory imports: all import paths need to point to a filename instead.

Recommended workaround: <Link href="#ssr-noexternal" />.


### ERR_UNKNOWN_FILE_EXTENSION

Another common problem is code importing `.css` or `.ts` files which make Node.js crash:

<CodeBlockTransformer lineBreak="white-space">
```
Error: ERR_UNKNOWN_FILE_EXTENSION .css node_modules/some-library/dist/main.css
    at someFunction (node_modules/some-library/dist/main.js)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:605:26)
```
</CodeBlockTransformer>

Recommended workaround: <Link href="#ssr-noexternal" />.


### Cannot read properties of undefined

The error `Cannot read properties of undefined` is often caused by ESM/CJS issues.

<CodeBlockTransformer lineBreak="white-space">
```
TypeError: Cannot read properties of undefined (reading 'someProp')
    at someFunction (node_modules/some-good-lib/dist/index.js:1000:3)
    at someHook (renderer/+someHook.js:13:37)
```
</CodeBlockTransformer>

> The underlying issue is often this:
> ```js
> // node_modules/some-good-lib/dist/index.js
>
> // Because of CJS/ESM issues, someImport is undefined
> import { someImport } from 'some-broken-lib'
>
> // ...
>
> function someFunction() {
>   // TypeError: Cannot read properties of undefined (reading 'someProp')
>   someImport.someProp
> }
> ```
>
> Sometimes, when dependency injection is used, the `import` to `some-broken-lib` isn't in the file in which the
> exception is being raised, making it harder to understand which library is broken. See
> [here](https://github.com/vikejs/vike/discussions/1235) an example of this.

Adding `some-broken-lib` to <Link href="#ssr-noexternal">`ssr.noExternal`</Link> usually solves the issue.

Alternatively, you can add `some-good-lib` to `ssr.noExternal` while adding `some-broken-lib` to <Link href="#vite-plugin-cjs-interop">`vite-plugin-cjs-interop`</Link>.



### React invalid component

The following is a common React error and the root cause is usually a CJS/ESM issue.

<CodeBlockTransformer lineBreak="white-space">
```
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
    at renderElement (node_modules/react-dom/...)
    at renderNodeDestructiveImpl (node_modules/react-dom/...)
    at renderNodeDestructive (node_modules/react-dom/...)
    ...
```
</CodeBlockTransformer>
Or `got: object`.
<CodeBlockTransformer lineBreak="white-space">
```
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
```
</CodeBlockTransformer>

React usually logs a component trace before throwing the error:

```
Check your code at +Page.tsx:26.
    at Page
    at div
    at div
    at Layout (/pages/+Layout.tsx:66:19)
```

> Sometimes React doesn't log a component trace. In that case you can use [this temporary patch](https://gist.github.com/brillout/c27b780f009d141acec7bda29d136e6e) to get a component trace.
>
> You can also use the temporary patch to get a more precise component trace. (For example the component trace above says `Check your code at +Page.tsx:26` but there can be hundreds of `+Page.tsx` files.)

Use the component trace to find out which component is `undefined` / an `object`. You'll likely see that the component is imported and that the import value is `undefined` / an `object` (instead of a React component) because of CJS/ESM interoperability quirks.

A local workaround is usually this:

```js
import SomeComponent from "some-npm-package"// [!code --]
import pkg from "some-npm-package"// [!code ++]
// This:// [!code ++]
const { SomeComponent } = pkg// [!code ++]
// Or that:// [!code ++]
const SomeComponent = pkg.default// [!code ++]
```

For a workaround that is global (and TypeScript friendly), see <Link href="#vite-plugin-cjs-interop" />.
